#include "types/record_info.h"

struct Info
{
	UINT32 num_blocks;
	BYTE num_slots;		
	BYTE free_slots;
};	

HashMap<string, Info,StringCompare> rec_info;

static void write_info_file(string tablename, Info i)
{
	int fd = open("dbs/pagedir", O_RDWR);
	int str_len = tablename.length();
	lseek(fd,0,SEEK_END);
	write(fd, &str_len, sizeof(UINT32));
	write(fd,tablename.c_str(), str_len + 1);
	write(fd,&i.num_blocks,sizeof(UINT32));
	write(fd,&i.num_slots,sizeof(BYTE));
	write(fd,&i.free_slots,sizeof(BYTE));
	close(fd);
}

void commit_rec_info(string &tablename, UINT32 block_num)
{
	//For now write everything..
	//Actually commit should happen only when the corresponding block is written
	Info r=  rec_info.lookup(tablename);
	HashMap<string, Info,StringCompare> temp_info;	
	load_file(temp_info);
	temp_info.insert(tablename, r);

	int fd = open("dbs/pagedir", O_WRONLY | O_TRUNC);
	close(fd);
	temp_info.foreach(write_info_file);
}

RecordInfo::RecordInfo()
{
	BufferManager &bm = BufferManager::inst();
	bm.reg_commit_callback(commit_rec_info);
	load_file(rec_info);
}

RecordInfo::~RecordInfo()
{
}

void load_file(HashMap<string, Info,StringCompare> & rec_info)
{
	bool bLoaded = true;
	int fd = 0;
	UINT32 len;
	UINT32 num_bytes;
	char tblname[10];
	BYTE free_slots;
	int num_blocks;
	BYTE num_slots;
		
	do
	{
		fd = open("dbs/pagedir", O_RDONLY | O_CREAT , S_IRUSR | S_IWUSR |S_IRGRP | S_IWGRP |S_IROTH | S_IWOTH );
		if(fd<0)
			break;
		
		while(1)		
		{
			num_bytes = read(fd, &len, sizeof(UINT32));
			
			if(num_bytes < sizeof(UINT32))
			{
				//file is empty
				break;
			}
			bLoaded = false;
			
			if((len == 0) || (len > 3))
				break;
			
			if(read(fd, &tblname, len + 1) < len + 1)
				break;
			tblname[len] = '\0';
			
			if(read(fd, &num_blocks, sizeof(UINT32)) < sizeof(UINT32))
				break;
				
			if(read(fd, &num_slots, sizeof(BYTE)) < sizeof(BYTE))
				break;
				
			if(read(fd, &free_slots, sizeof(BYTE)) < sizeof(BYTE))
				break;
			
			Info i = {num_blocks, num_slots,free_slots};
			string tbl(tblname);
			rec_info.insert(tbl, i);
			bLoaded = true;
		}
	}while(0);
	
	if(fd >0)
	{
			close(fd);
	}
	if(!bLoaded)
	{
		throw Exception(recinfo_load_failed);
	}
}

UINT32 RecordInfo::get_num_blocks(string &tblname)
{
	return rec_info.lookup(tblname).num_blocks;
}

BYTE RecordInfo::get_max_num_records(string &tblname)
{
	return rec_info.lookup(tblname).num_slots;
}

BYTE RecordInfo::get_num_records(string &tblname, UINT32 blocknum)
{
	return rec_info.lookup(tblname).num_slots - rec_info.lookup(tblname).free_slots;
}

UINT32 RecordInfo::get_record_size(string &tblname)
{
	return BLOCK_SIZE / rec_info.lookup(tblname).num_slots;
}

RecordId RecordInfo::get_empty_record(string &tblname)
{
	Info i = rec_info.lookup(tblname);
	
	if(i.free_slots == 0)
	{
		//When there are no free slots allocate new block
		i.num_blocks++;
		i.free_slots = i.num_slots;
	}
	
	RecordId id = {i.num_blocks - 1,i.num_slots - i.free_slots};
	i.free_slots--;
	rec_info.insert(tblname, i);
	return id;
}

void RecordInfo::set_table_info(string &tblname, BYTE num_slots)
{
	Info i = {0,num_slots,0};
	if(num_slots == 0)
		throw Exception(recinfo_recsize_large);
	try
	{	
		rec_info.lookup(tblname);
	}
	catch(...)
	{
		i.num_slots = num_slots;
		rec_info.insert(tblname, i);
		return;
	}
	
	throw Exception(recinfo_duplicate_table);
}
